// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: hyuan

#include "coin2/exchange/phemex_futures/order_v1/parser.h"

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/phemex/order_v1/error_code.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::phemex_futures::order_v1 {

namespace {

using namespace coin2::exchange::phemex::order_v1::error_code;

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::CurrencyTransferElement;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;
using coin::proto::TransferRequestProto;
using coin2::exchange::base::order::IOrderContext;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  if (data.HasMember("clOrdID") && data["clOrdID"].IsString()) {
    if (!data["clOrdID"].IsNull()) {
      return stoll(data["clOrdID"].GetString());
    }
  }
  return 0;
}

inline int64_t GetHedgedProcOrderId(const JsonValue& data) {
  if (data.HasMember("clOrdId") && data["clOrdId"].IsString()) {
    if (!data["clOrdId"].IsNull()) {
      return stoll(data["clOrdId"].GetString());
    }
  }
  return 0;
}

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "Buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "Sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "Limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "Market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if (status == "Rejected") {
    order->set_is_live(false);
  } else if (
      (status == "New") || (status == "Created") || (status == "Init") ||
      (status == "PartiallyFilled") || (status == "Untriggered") || (status == "Triggered")) {
    order->set_is_live(true);
  } else if (status == "Canceled" || status == "Deactivated") {
    order->set_cancel_confirmed(true);
  } else if (status == "Filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "Unknown order status!" << status;
  }
}

bool IsPhemexFuturesErrMessage(const JsonValue& doc) {
  if (doc.HasMember("code")) {
    return (std::string(doc["code"].GetString()) != "0");
  }

  return false;
}

bool IsPhemexFuturesErrMessage10002(const JsonValue& doc) {
  if (doc.HasMember("code")) {
    return (std::string(doc["code"].GetString()) == "10002");
  }

  if (doc.HasMember("msg")) {
    return (std::string(doc["msg"].GetString()) == "OM_ORDER_NOT_FOUND");
  }

  return false;
}

}  // namespace

void PhemexFuturesParser::ParseRestAccountInfo() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "account":{
              "userID":4705420,
              "accountId":47054200002,
              "currency":"USD",
              "accountBalanceEv":1000456,
              "totalUsedBalanceEv":31162,
              "bonusBalanceEv":0
          },
          "positions":[
              {
                  "userID":4705420,
                  "accountID":47054200002,
                  "symbol":"XRPUSD",
                  "currency":"USD",
                  "side":"Sell",
                  "positionStatus":"Normal",
                  "crossMargin":false,
                  "leverageEr":-1000000000,
                  "leverage":-10,
                  "initMarginReqEr":10000000,
                  "initMarginReq":0.1,
                  "maintMarginReqEr":1000000,
                  "maintMarginReq":0.01,
                  "riskLimitEv":2000000000,
                  "riskLimit":200000,
                  "size":9,
                  "value":22.8945,
                  "valueEv":228945,
                  "avgEntryPriceEp":5088,
                  "avgEntryPrice":0.5088,
                  "posCostEv":23046,
                  "posCost":2.3046,
                  "assignedPosBalanceEv":23421,
                  "assignedPosBalance":2.3421,
                  "bankruptCommEv":733,
                  "bankruptComm":0.0733,
                  "bankruptPriceEp":27131,
                  "bankruptPrice":2.7131,
                  "positionMarginEv":991982,
                  "positionMargin":99.1982,
                  "liquidationPriceEp":27080,
                  "liquidationPrice":2.708,
                  "deleveragePercentileEr":0,
                  "deleveragePercentile":0,
                  "buyValueToCostEr":10114000,
                  "buyValueToCost":0.10114,
                  "sellValueToCostEr":10126000,
                  "sellValueToCost":0.10126,
                  "markPriceEp":5096,
                  "markPrice":0.5096,
                  "estimatedOrdLossEv":0,
                  "estimatedOrdLoss":0,
                  "usedBalanceEv":31162,
                  "usedBalance":3.1162,
                  "cumClosedPnlEv":0,
                  "cumFundingFeeEv":0,
                  "cumTransactFeeEv":-7,
                  "transactTimeNs":1681381398621430126,
                  "takerFeeRateEr":40000,
                  "makerFeeRateEr":-2500,
                  "term":3,
                  "lastTermEndTimeNs":1681380917947159034,
                  "lastFundingTimeNs":1681380886563963696,
                  "curTermRealisedPnlEv":3,
                  "execSeq":14714346686,
                  "freeQty":9,
                  "freeCostEv":7741,
                  "buyLeavesValueEv":0,
                  "sellLeavesValueEv":76440
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  bool account_balance_found = false;
  if (account_balance.has_exchange() && account_balance.has_market_type()) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
    *result_.mutable_account_balance() = std::move(account_balance);
    account_balance_found = true;
  }

  auto account_position = ParsePosition(document);
  bool account_position_found = false;
  if (account_position.has_exchange() && account_position.has_market_type()) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
    *result_.mutable_account_position() = std::move(account_position);
    account_position_found = true;
  }

  if (!account_balance_found && !account_position_found) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
  }
}

void PhemexFuturesParser::ParseHedgedRestAccountInfo() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "account":{
              "userID":4705420,
              "accountId":47054200003,
              "currency":"USDT",
              "accountBalanceRv":"150.0281486525",
              "totalUsedBalanceRv":"3.013525636",
              "bonusBalanceRv":"0"
          },
          "positions":[
              {
                  "userID":4705420,
                  "accountID":47054200003,
                  "symbol":"BTCUSDT",
                  "currency":"USDT",
                  "side":"Buy",
                  "positionStatus":"Normal",
                  "crossMargin":false,
                  "leverageRr":"-10",
                  "initMarginReqRr":"0.1",
                  "maintMarginReqRr":"0.005",
                  "riskLimitRv":"2000000",
                  "size":"0.001",
                  "valueRv":"29.9734",
                  "avgEntryPriceRp":"29973.4",
                  "avgEntryPrice":"29973.4",
                  "posCostRv":"3.013525636",
                  "assignedPosBalanceRv":"3.013525636",
                  "bankruptCommRv":"0.00000006",
                  "bankruptPriceRp":"0.1",
                  "positionMarginRv":"150.0281485925",
                  "liquidationPriceRp":"0.1",
                  "deleveragePercentileRr":"0",
                  "buyValueToCostRr":"0.10114",
                  "sellValueToCostRr":"0.10126",
                  "markPriceRp":"30007.759095033",
                  "estimatedOrdLossRv":"0",
                  "usedBalanceRv":"3.013525636",
                  "cumClosedPnlRv":"0.0259",
                  "cumFundingFeeRv":"0",
                  "cumTransactFeeRv":"-0.0022486525",
                  "transactTimeNs":1681295001452950285,
                  "takerFeeRateRr":"0.0004",
                  "makerFeeRateRr":"-0.000025",
                  "term":1,
                  "lastTermEndTimeNs":0,
                  "lastFundingTimeNs":1681286400000000000,
                  "curTermRealisedPnlRv":"0.0281486525",
                  "execSeq":1981166154,
                  "posSide":"Merged",
                  "posMode":"OneWay",
                  "buyLeavesValueRv":"0",
                  "sellLeavesValueRv":"30.0091"
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseHedgedRestAccountInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseHedgedBalance(document);
  bool account_balance_found = false;
  if (account_balance.has_exchange() && account_balance.has_market_type()) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
    *result_.mutable_account_balance() = std::move(account_balance);
    account_balance_found = true;
  }

  auto account_position = ParseHedgedPosition(document);
  bool account_position_found = false;
  if (account_position.has_exchange() && account_position.has_market_type()) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
    *result_.mutable_account_position() = std::move(account_position);
    account_position_found = true;
  }

  if (!account_balance_found && !account_position_found) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
  }
}

AccountBalance PhemexFuturesParser::ParseBalance(const JsonValue& doc) {
  AccountBalance acc_balance;

  if (doc.IsObject() && IsPhemexFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return acc_balance;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("account") ||
      !doc["data"]["account"].IsObject()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return acc_balance;
  }

  const JsonValue& account = doc["data"]["account"];
  if (!account.HasMember("currency") || !account.HasMember("accountBalanceEv") ||
      !account.HasMember("totalUsedBalanceEv")) {
    LOG(ERROR) << "[ParseBalance] Invalid balance data\n" << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Phemex");
  acc_balance.set_market_type("Futures");
  if (symcache_->has_currency_native(account["currency"].GetString())) {
    auto* currency = symcache_->GetCurrencyByNative(account["currency"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(account["accountBalanceEv"].GetString()) /
                 ((currency->currency() == "BTC" || currency->currency() == "ETH") ? 1e8 : 1e4);
    auto hold = stod(account["totalUsedBalanceEv"].GetString()) /
                ((currency->currency() == "BTC" || currency->currency() == "ETH") ? 1e8 : 1e4);
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);
  }

  return acc_balance;
}

AccountBalance PhemexFuturesParser::ParseHedgedBalance(const JsonValue& doc) {
  AccountBalance acc_balance;

  if (doc.IsObject() && IsPhemexFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseHedgedBalance] Error message\n" << JsonToString(doc);
    return acc_balance;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("account") ||
      !doc["data"]["account"].IsObject()) {
    LOG(ERROR) << "[ParseHedgedBalance] Invalid data format\n" << JsonToString(doc);
    return acc_balance;
  }

  const JsonValue& account = doc["data"]["account"];
  if (!account.HasMember("currency") || !account.HasMember("accountBalanceRv") ||
      !account.HasMember("totalUsedBalanceRv")) {
    LOG(ERROR) << "[ParseHedgedBalance] Invalid balance data\n" << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Phemex");
  acc_balance.set_market_type("Futures");
  if (symcache_->has_currency_native(account["currency"].GetString())) {
    auto* currency = symcache_->GetCurrencyByNative(account["currency"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(account["accountBalanceRv"].GetString());
    auto hold = stod(account["totalUsedBalanceRv"].GetString());
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);
  }

  return acc_balance;
}

bool PhemexFuturesParser::ParseRawPosition(
    const JsonValue& positions,
    RawPositionMap* raw_position_map) {
  for (const auto& data : positions.GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("size")) {
      LOG(ERROR) << "[ParseRawPosition] Invalid position data\n" << JsonToString(positions);
      continue;
    }
    auto native_symbol = data["symbol"].GetString();
    auto& position = (*raw_position_map)[native_symbol];
    double pos_amt = stod(data["size"].GetString());
    if (std::string(data["side"].GetString()) == "Sell") {
      position.set_short_position(pos_amt);
      pos_amt = -pos_amt;
    } else {
      position.set_long_position(pos_amt);
    }
    position.set_net_position(pos_amt);
  }

  return true;
}

AccountPosition PhemexFuturesParser::ParsePosition(const JsonValue& doc) {
  AccountPosition acc_position;

  if (doc.IsObject() && IsPhemexFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] Error message\n" << JsonToString(doc);
    return acc_position;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("positions") ||
      !doc["data"]["positions"].IsArray()) {
    LOG(ERROR) << "[ParsePosition] Invalid data format\n" << JsonToString(doc);
    return acc_position;
  }

  RawPositionMap raw_position_map;
  if (!ParseRawPosition(doc["data"]["positions"], &raw_position_map)) {
    return acc_position;
  }

  acc_position.set_exchange("Phemex");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }

  return acc_position;
}

AccountPosition PhemexFuturesParser::ParseHedgedPosition(const JsonValue& doc) {
  AccountPosition acc_position;

  if (doc.IsObject() && IsPhemexFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseHedgedPosition] Error message\n" << JsonToString(doc);
    return acc_position;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("positions") ||
      !doc["data"]["positions"].IsArray()) {
    LOG(ERROR) << "[ParseHedgedPosition] Invalid data format\n" << JsonToString(doc);
    return acc_position;
  }

  RawPositionMap raw_position_map;
  if (!ParseRawPosition(doc["data"]["positions"], &raw_position_map)) {
    return acc_position;
  }

  acc_position.set_exchange("Phemex");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }

  return acc_position;
}

void PhemexFuturesParser::ParseRestOpenOrders() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "rows":[
              {
                  "bizError":0,
                  "orderID":"35f90254-1ff0-440f-890e-9b2bf9f67814",
                  "clOrdID":"1681382397183044865",
                  "symbol":"XRPUSD",
                  "side":"Buy",
                  "actionTimeNs":1681382461696975462,
                  "transactTimeNs":1681382461703225511,
                  "orderType":"Limit",
                  "priceEp":5102,
                  "price":0.5102,
                  "orderQty":3,
                  "displayQty":0,
                  "timeInForce":"PostOnly",
                  "reduceOnly":false,
                  "closedPnlEv":0,
                  "closedPnl":0,
                  "closedSize":0,
                  "cumQty":0,
                  "cumValueEv":0,
                  "cumValue":0,
                  "leavesQty":3,
                  "leavesValueEv":76530,
                  "leavesValue":7.653,
                  "stopDirection":"UNSPECIFIED",
                  "stopPxEp":0,
                  "stopPx":0,
                  "trigger":"UNSPECIFIED",
                  "pegOffsetValueEp":0,
                  "pegOffsetProportionEr":0,
                  "execStatus":"New",
                  "pegPriceType":"UNSPECIFIED",
                  "ordStatus":"New",
                  "execInst":"None"
              }
          ],
          "nextPageArg":""
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document) || !document.IsObject()) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsPhemexFuturesErrMessage10002(document)) {
    LOG(INFO) << "[ParseRestOpenOrders] Error message 10002 found\n" << JsonToString(document);
    result_.mutable_order_list()->Clear();
    result_.set_type(MsgType::ORDER_STATUS);
    return;
  }

  if (IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e4);
    order->set_qty(stod(data["orderQty"].GetString()));
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["orderType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseHedgedRestOpenOrders() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "rows":[
              {
                  "bizError":0,
                  "orderID":"1f6031f8-878b-4c0c-afee-c1b6fc52aedb",
                  "clOrdID":"1681294786397180273",
                  "symbol":"BTCUSDT",
                  "side":"Sell",
                  "actionTimeNs":1681295004463178684,
                  "transactTimeNs":1681295004466612296,
                  "orderType":"Limit",
                  "priceRp":"30009",
                  "orderQtyRq":"0.001",
                  "displayQtyRq":"0.001",
                  "timeInForce":"PostOnly",
                  "reduceOnly":false,
                  "closedPnlRv":"0",
                  "closedSizeRq":"0",
                  "cumQtyRq":"0",
                  "cumValueRv":"0",
                  "leavesQtyRq":"0.001",
                  "leavesValueRv":"30.009",
                  "stopDirection":"UNSPECIFIED",
                  "stopPxRp":"0",
                  "trigger":"UNSPECIFIED",
                  "pegOffsetValueRp":"0",
                  "pegOffsetProportionRr":"0",
                  "execStatus":"New",
                  "pegPriceType":"UNSPECIFIED",
                  "ordStatus":"New",
                  "execInst":"None",
                  "takeProfitRp":"0",
                  "stopLossRp":"0"
              }
          ],
          "nextPageArg":""
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document) || !document.IsObject()) {
    LOG(ERROR) << "[ParseHedgedRestOpenOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsPhemexFuturesErrMessage10002(document)) {
    LOG(INFO) << "[ParseHedgedRestOpenOrders] Error message 10002 found\n"
              << JsonToString(document);
    result_.mutable_order_list()->Clear();
    result_.set_type(MsgType::ORDER_STATUS);
    return;
  }

  if (IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseHedgedRestOpenOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseHedgedRestOpenOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1-linear");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceRp"].GetString()));
    order->set_qty(stod(data["orderQtyRq"].GetString()));
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["orderType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseRestHistoryOrders() {
  /*
  {
      "code":0,
      "msg":"OK",
      "data":{
          "total":-1,
          "rows":[
              {
                  "orderID":"3ce03692-613c-4967-9011-c56d7b66b54a",
                  "clOrdID":"1681380997608338784",
                  "symbol":"XRPUSD",
                  "side":"Sell",
                  "orderType":"Limit",
                  "actionTimeNs":1681381127789347428,
                  "priceEp":5086,
                  "price":null,
                  "orderQty":3,
                  "displayQty":0,
                  "timeInForce":"PostOnly",
                  "reduceOnly":false,
                  "takeProfitEp":0,
                  "takeProfit":null,
                  "stopLossEp":0,
                  "closedPnlEv":0,
                  "closedPnl":null,
                  "closedSize":0,
                  "cumQty":0,
                  "cumValueEv":0,
                  "cumValue":null,
                  "leavesQty":0,
                  "leavesValueEv":0,
                  "leavesValue":null,
                  "stopLoss":null,
                  "stopDirection":"UNSPECIFIED",
                  "ordStatus":"Canceled",
                  "transactTimeNs":1681381127792187792,
                  "bizError":0
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e4);
    order->set_qty(stod(data["orderQty"].GetString()));
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["orderType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseHedgedRestHistoryOrders() {
  /*
  {
      "code":0,
      "msg":"OK",
      "data":{
          "rows":[
              {
                  "orderId":"c68c453f-d60e-4337-9730-0fd6c0e7299e",
                  "clOrdId":"1681356476394793961",
                  "symbol":"LTCUSDT",
                  "side":"Buy",
                  "ordType":"Limit",
                  "actionTimeNs":1681357410699667097,
                  "priceRp":"92.61",
                  "orderQtyRq":"0.02",
                  "displayQtyRq":"0.02",
                  "timeInForce":"PostOnly",
                  "reduceOnly":false,
                  "takeProfitRp":"0",
                  "stopLossRp":"0",
                  "closedPnlRv":"-0.00637647",
                  "closedSizeRq":"0.02",
                  "cumQtyRq":"0.02",
                  "cumValueRv":"1.8522",
                  "leavesQtyRq":"0",
                  "leavesValueRv":"0",
                  "stopDirection":"UNSPECIFIED",
                  "ordStatus":"Filled",
                  "transactTimeNs":1681357411874890463,
                  "bizError":0
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseHedgedRestHistoryOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseHedgedRestHistoryOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseHedgedRestHistoryOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1-linear");
    order->set_exchange_order_id(data["orderId"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceRp"].GetString()));
    order->set_qty(stod(data["orderQtyRq"].GetString()));
    order->set_proc_order_id(GetHedgedProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseRestFillOrders() {
  /*
  {
      "code":0,
      "msg":"OK",
      "data":{
          "total":-1,
          "rows":[
              {
                  "transactTimeNs":1681381855826557539,
                  "symbol":"XRPUSD",
                  "currency":"USD",
                  "action":"New",
                  "side":"Sell",
                  "tradeType":"Trade",
                  "execQty":3,
                  "execPriceEp":5098,
                  "orderQty":3,
                  "priceEp":5098,
                  "execValueEv":76470,
                  "feeRateEr":-2500,
                  "execFeeEv":-1,
                  "closedSize":0,
                  "closedPnlEv":0,
                  "ordType":"Limit",
                  "execID":"eb48f166-79c9-56d4-9a5d-e106c1b505ca",
                  "orderID":"47600e83-3a50-475b-89f9-fbb915ab5f16",
                  "clOrdID":"1681381313332610404",
                  "execStatus":"MakerFill"
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestFillOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    std::string symbol = data["symbol"].GetString();
    fill->set_symbol(symbol);
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["priceEp"].GetString()) / 1e4);
    fill->set_qty(stod(data["orderQty"].GetString()));
    fill->set_fill_id(data["execID"].GetString());
    fill->set_fee(
        stod(data["execFeeEv"].GetString()) /
        ((symbol == "BTCUSD" || symbol == "cETHUSD") ? 1e8 : 1e4));
    std::string exec_status = data["execStatus"].GetString();
    if (exec_status == "MakerFill") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (exec_status == "TakerFill") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexFuturesParser::ParseHedgedRestFillOrders() {
  /*
  {
      "code":0,
      "msg":"OK",
      "data":{
          "rows":[
              {
                  "transactTimeNs":1681292865968285111,
                  "symbol":"BTCUSDT",
                  "currency":"USDT",
                  "action":"New",
                  "side":"Buy",
                  "tradeType":"Trade",
                  "execQtyRq":"0.001",
                  "execPriceRp":"29969.8",
                  "orderQtyRq":"0.001",
                  "priceRp":"29969.8",
                  "execValueRv":"29.9698",
                  "feeRateRr":"-0.000025",
                  "execFeeRv":"-0.00074925",
                  "closedSizeRq":"0",
                  "closedPnlRv":"0",
                  "ordType":"Limit",
                  "execID":"e563d7a0-7456-5251-8aae-236cf64bfddf",
                  "orderID":"f12f0383-bded-483a-a008-be471994d64a",
                  "clOrdID":"1681292720110785456",
                  "execStatus":"MakerFill"
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseHedgedRestFillOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseHedgedRestFillOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseHedgedRestFillOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    std::string symbol = data["symbol"].GetString();
    fill->set_symbol(symbol);
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["priceRp"].GetString()));
    fill->set_qty(stod(data["orderQtyRq"].GetString()));
    fill->set_fill_id(data["execID"].GetString());
    fill->set_fee(stod(data["execFeeRv"].GetString()));
    std::string exec_status = data["execStatus"].GetString();
    if (exec_status == "MakerFill") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (exec_status == "TakerFill") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexFuturesParser::ParseRestTransfer() {
  /*
  {
      "code": 0,
      "msg": "OK",
      "data": {
          "total": 589,
          "rows": [
          {
              "moveOp": 1,
              "fromCurrencyName": "BTC",
              "toCurrencyName": "BTC",
              "fromAmount": "0.10000000",
              "toAmount": "0.10000000",
              "linkKey": "2431ca9b-2dd4-44b8-91f3-2539bb62db2d",
              "status": 10,
              "createTime": 1580201427000
          }]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestTransfer] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexFuturesErrMessage(document)) {
    LOG(ERROR) << "[ParseRestTransfer] Error message\n" << JsonToString(document);
    return;
  }

  // TODO(hyuan): Response is not as described; further test is needed.
  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestTransfer] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    if (!data.HasMember("fromCurrencyName") || !data.HasMember("moveOp") ||
        !data.HasMember("fromAmount")) {
      LOG(ERROR) << "[ParseRestTransfer] Invalid transfer format";
      continue;
    }
    int64_t move_op = stoll(data["moveOp"].GetString());
    std::string currency_name;
    if ((move_op == 1) || (move_op == 4)) {
      currency_name = data["toCurrencyName"].GetString();
    } else if ((move_op == 2) || (move_op == 3)) {
      currency_name = data["fromCurrencyName"].GetString();
    }
    if (!symcache_->has_currency_native(currency_name)) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(currency_name);
    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_currency(currency->currency());
    if ((move_op == 1) || (move_op == 4)) {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT);
      currency_transfer->set_qty(stod(data["toAmount"].GetString()));
    } else if ((move_op == 2) || (move_op == 3)) {
      currency_transfer->set_type(TransferRequestProto::WITHDRAW);
      currency_transfer->set_qty(stod(data["fromAmount"].GetString()));
    }
    if (stoll(data["status"].GetString()) == 10) {
      currency_transfer->set_status(TransferRequestProto::TRANSFER_CONFIRMED);
    }
    currency_transfer->set_transfer_id(data["linkKey"].GetString());
    currency_transfer_bundle_found = true;
  }

  if (currency_transfer_bundle_found) {
    result_.set_type(MsgType::TRANSFER);
  }
}

void PhemexFuturesParser::ParseHeader(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto& header = http_context->res.base();
  rate_limit_contract_.success = false;
  enable_time_contract_.success = false;
  rate_limit_others_.success = false;
  enable_time_others_.success = false;

  if (header.count("x-ratelimit-remaining-contract") > 0) {
    rate_limit_contract_.rate_limit_remain =
        std::stoi(header.at("x-ratelimit-remaining-contract").data());
    rate_limit_contract_.success = true;
  }

  if (header.count("x-ratelimit-retry-after-contract") > 0) {
    int32_t retry_after = std::stoi(header.at("x-ratelimit-retry-after-contract").data());
    if (retry_after > 0) {
      enable_time_contract_.enable_time =
          (retry_after + 2) * 1'000'000'000LL + GetCurrentTimestamp();
      enable_time_contract_.success = true;
      LOG(INFO) << "phemex native client enable_time for contract: "
                << enable_time_contract_.enable_time;
    }
  }

  if (header.count("x-ratelimit-remaining") > 0) {
    rate_limit_others_.rate_limit_remain = std::stoi(header.at("x-ratelimit-remaining").data());
    rate_limit_others_.success = true;
  }

  if (header.count("x-ratelimit-retry-after") > 0) {
    int32_t retry_after = std::stoi(header.at("x-ratelimit-retry-after").data());
    if (retry_after > 0) {
      enable_time_others_.enable_time = (retry_after + 2) * 1'000'000'000LL + GetCurrentTimestamp();
      enable_time_others_.success = true;
      LOG(INFO) << "phemex native client enable_time for others: "
                << enable_time_others_.enable_time;
    }
  }
}

void PhemexFuturesParser::ParseSubmitResponse() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "bizError":0,
          "orderID":"b64d7e7e-3bf1-4b5b-b840-dd59681eedb6",
          "clOrdID":"1681294786397180266",
          "symbol":"BTCUSDT",
          "side":"Buy",
          "actionTimeNs":1681294981291112019,
          "transactTimeNs":1681294981291112019,
          "orderType":"Limit",
          "priceRp":"30004.4",
          "orderQtyRq":"0.001",
          "displayQtyRq":"0.001",
          "timeInForce":"PostOnly",
          "reduceOnly":false,
          "closedPnlRv":"0",
          "closedSizeRq":"0",
          "cumQtyRq":"0",
          "cumValueRv":"0",
          "leavesQtyRq":"0.001",
          "leavesValueRv":"30.0044",
          "stopDirection":"UNSPECIFIED",
          "stopPxRp":"0",
          "trigger":"UNSPECIFIED",
          "pegOffsetValueRp":"0",
          "pegOffsetProportionRr":"0",
          "execStatus":"PendingNew",
          "pegPriceType":"UNSPECIFIED",
          "ordStatus":"Created",
          "execInst":"None",
          "takeProfitRp":"0",
          "stopLossRp":"0"
      }
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  const auto http_code = http_context->res.result_int();
  if (http_code == 429) {
    LOG(ERROR) << "[ParseSubmitResponse] API limit error";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(http_context->res.body().data());
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  if (json.HasMember("code") && json.HasMember("msg")) {
    auto error_code =
        GetOrderErrorCode(json["code"], json["msg"], order_context->has_exchange_order_id());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
      return;
    }
  }

  if (!json.HasMember("data") || !json["data"].HasMember("symbol") ||
      !json["data"].HasMember("orderID")) {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::INVALID_JSON);
    return;
  }

  resp->set_exchange_order_id(std::string(json["data"]["orderID"].GetString()));
  resp->set_success(true);
}

void PhemexFuturesParser::ParseCancelResponse() {
  /*
  {
      "code":0,
      "msg":"",
      "data":{
          "bizError":0,
          "orderID":"3bc89e91-1590-4865-8caa-0f23a44b8283",
          "clOrdID":"1681294786397180268",
          "symbol":"BTCUSDT",
          "side":"Sell",
          "actionTimeNs":1681294990951139766,
          "transactTimeNs":1681294990951139766,
          "orderType":"Limit",
          "priceRp":"30009.3",
          "orderQtyRq":"0.001",
          "displayQtyRq":"0.001",
          "timeInForce":"PostOnly",
          "reduceOnly":false,
          "closedPnlRv":"0",
          "closedSizeRq":"0",
          "cumQtyRq":"0",
          "cumValueRv":"0",
          "leavesQtyRq":"0.001",
          "leavesValueRv":"30.0093",
          "stopDirection":"UNSPECIFIED",
          "stopPxRp":"0",
          "trigger":"UNSPECIFIED",
          "pegOffsetValueRp":"0",
          "pegOffsetProportionRr":"0",
          "execStatus":"PendingCancel",
          "pegPriceType":"UNSPECIFIED",
          "ordStatus":"New",
          "execInst":"None",
          "takeProfitRp":"0",
          "stopLossRp":"0"
      }
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  const auto http_code = http_context->res.result_int();
  if (http_code == 429 || http_code == 418) {
    LOG(ERROR) << "[ParseCancelResponse] API limit error";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(http_context->res.body().data());
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  if (json.HasMember("code") && json.HasMember("msg")) {
    auto error_code =
        GetOrderErrorCode(json["code"], json["msg"], order_context->has_exchange_order_id());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
      return;
    }
  }

  if (!json.HasMember("data") || !json["data"].HasMember("symbol") ||
      !json["data"].HasMember("orderID")) {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::INVALID_JSON);
    return;
  }

  resp->set_exchange_order_id(std::string(json["data"]["orderID"].GetString()));
  resp->set_success(true);
  resp->set_cancel_confirmed(true);
}

void PhemexFuturesParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  "orders":[
      {
          "accountID":"47054200002",
          "action":"New",
          "actionBy":"ByUser",
          "actionTimeNs":"1681381873617618675",
          "addedSeq":"14714504136",
          "bonusChangedAmountEv":"0",
          "clOrdID":"1681381313332610413",
          "closedPnlEv":"0",
          "closedSize":"0",
          "code":"0",
          "cumQty":"0",
          "cumValueEv":"0",
          "curAccBalanceEv":"1000462",
          "curAssignedPosBalanceEv":"70371",
          "curBonusBalanceEv":"0",
          "curLeverageEr":"-1000000000",
          "curPosSide":"Sell",
          "curPosSize":"27",
          "curPosTerm":"3",
          "curPosValueEv":"687615",
          "curRiskLimitEv":"2000000000",
          "currency":"USD",
          "cxlRejReason":"0",
          "displayQty":"0",
          "execFeeEv":"0",
          "execID":"00000000-0000-0000-0000-000000000000",
          "execPriceEp":"0",
          "execQty":"0",
          "execSeq":"14714504136",
          "execStatus":"New",
          "execValueEv":"0",
          "feeRateEr":"0",
          "leavesQty":"3",
          "leavesValueEv":"76500",
          "message":"No error",
          "ordStatus":"New",
          "ordType":"Limit",
          "orderID":"e476cf1d-5f89-4bd4-8e6a-8d7156423878",
          "orderQty":"3",
          "pegOffsetValueEp":"0",
          "priceEp":"5100",
          "relatedPosTerm":"3",
          "relatedReqNum":"225",
          "side":"Sell",
          "slTrigger":"ByMarkPrice",
          "stopLossEp":"0",
          "stopPxEp":"0",
          "symbol":"XRPUSD",
          "takeProfitEp":"0",
          "timeInForce":"PostOnly",
          "tpTrigger":"ByLastPrice",
          "transactTimeNs":"1681381873622057379",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("orders") || !document["orders"].IsArray()) {
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["orders"].GetArray()) {
    if (!data.HasMember("ordStatus")) {
      LOG(ERROR) << "[ParseWsOrderInfoList] ordStatus not found";
      continue;
    }
    std::string action = data["action"].GetString();
    std::string ord_status = data["ordStatus"].GetString();
    if ((action != "New" && action != "Cancel") ||
        (action == "Cancel" && ord_status == "Rejected")) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e4);
    order->set_qty(stod(data["orderQty"].GetString()));
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseHedgedWsOrderInfoList(const JsonDocument& document) {
  /*
  "orders_p":[
      {
          "accountID":"47054200003",
          "action":"New",
          "actionBy":"ByUser",
          "actionTimeNs":"1681295883853734395",
          "addedSeq":"1981249828",
          "apRp":"0",
          "bonusChangedAmountRv":"0",
          "bpRp":"0",
          "clOrdID":"1681295872515201840",
          "cl_req_code":"0",
          "closedPnlRv":"0",
          "closedSize":"0",
          "code":"0",
          "cumFeeRv":"0",
          "cumQty":"0",
          "cumValueRv":"0",
          "curAccBalanceRv":"150.0288988225",
          "curAssignedPosBalanceRv":"6.045753367686",
          "curBonusBalanceRv":"0",
          "curLeverageRr":"-10",
          "curPosSide":"Buy",
          "curPosSize":"0.002",
          "curPosTerm":"1",
          "curPosValueRv":"59.9802",
          "curRiskLimitRv":"2000000",
          "currency":"USDT",
          "cxlRejReason":"0",
          "displayQty":"0.001",
          "execFeeRv":"0",
          "execID":"00000000-0000-0000-0000-000000000000",
          "execPriceRp":"0",
          "execQty":"0",
          "execSeq":"1981249828",
          "execStatus":"New",
          "execValueRv":"0",
          "feeRateRr":"0",
          "leavesQty":"0.001",
          "leavesValueRv":"29.9776",
          "message":"No error",
          "ordStatus":"New",
          "ordType":"Limit",
          "orderID":"0e7f6710-60fe-4d45-9ac2-8e533bd05dc5",
          "orderQty":"0.001",
          "pegOffsetProportionRr":"0",
          "pegOffsetValueRp":"0",
          "posSide":"Merged",
          "priceRp":"29977.6",
          "relatedPosTerm":"1",
          "relatedReqNum":"1470",
          "side":"Sell",
          "slTrigger":"ByMarkPrice",
          "stopLossRp":"0",
          "stopPxRp":"0",
          "symbol":"BTCUSDT",
          "takeProfitRp":"0",
          "timeInForce":"PostOnly",
          "tpTrigger":"ByLastPrice",
          "transactTimeNs":"1681295883856252940",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("orders_p") || !document["orders_p"].IsArray()) {
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["orders_p"].GetArray()) {
    if (!data.HasMember("ordStatus")) {
      LOG(ERROR) << "[ParseHedgedWsOrderInfoList] ordStatus not found";
      continue;
    }
    std::string action = data["action"].GetString();
    std::string ord_status = data["ordStatus"].GetString();
    if ((action != "New" && action != "Cancel") ||
        (action == "Cancel" && ord_status == "Rejected")) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Futures);
    order->set_api_version("v1-linear");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceRp"].GetString()));
    order->set_qty(stod(data["orderQty"].GetString()));
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexFuturesParser::ParseWsFillInfoList(const JsonDocument& document) {
  /*
  "orders":[
      {
          "accountID":"47054200002",
          "action":"New",
          "actionBy":"ByUser",
          "actionTimeNs":"1681381873617618675",
          "addedSeq":"14714504136",
          "bonusChangedAmountEv":"0",
          "clOrdID":"1681381313332610413",
          "closedPnlEv":"0",
          "closedSize":"0",
          "code":"0",
          "cumQty":"0",
          "cumValueEv":"0",
          "curAccBalanceEv":"1000462",
          "curAssignedPosBalanceEv":"70371",
          "curBonusBalanceEv":"0",
          "curLeverageEr":"-1000000000",
          "curPosSide":"Sell",
          "curPosSize":"27",
          "curPosTerm":"3",
          "curPosValueEv":"687615",
          "curRiskLimitEv":"2000000000",
          "currency":"USD",
          "cxlRejReason":"0",
          "displayQty":"0",
          "execFeeEv":"0",
          "execID":"00000000-0000-0000-0000-000000000000",
          "execPriceEp":"0",
          "execQty":"0",
          "execSeq":"14714504136",
          "execStatus":"New",
          "execValueEv":"0",
          "feeRateEr":"0",
          "leavesQty":"3",
          "leavesValueEv":"76500",
          "message":"No error",
          "ordStatus":"New",
          "ordType":"Limit",
          "orderID":"e476cf1d-5f89-4bd4-8e6a-8d7156423878",
          "orderQty":"3",
          "pegOffsetValueEp":"0",
          "priceEp":"5100",
          "relatedPosTerm":"3",
          "relatedReqNum":"225",
          "side":"Sell",
          "slTrigger":"ByMarkPrice",
          "stopLossEp":"0",
          "stopPxEp":"0",
          "symbol":"XRPUSD",
          "takeProfitEp":"0",
          "timeInForce":"PostOnly",
          "tpTrigger":"ByLastPrice",
          "transactTimeNs":"1681381873622057379",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("orders") || !document["orders"].IsArray()) {
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["orders"].GetArray()) {
    if (!data.HasMember("execStatus")) {
      LOG(ERROR) << "[ParseWsFillInfoList] execStatus not found";
      continue;
    }
    std::string exec_status = data["execStatus"].GetString();
    if (exec_status != "MakerFill" && exec_status != "TakerFill") {
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    std::string symbol = data["symbol"].GetString();
    fill->set_symbol(symbol);
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["priceEp"].GetString()) / 1e4);
    fill->set_qty(stod(data["orderQty"].GetString()));
    fill->set_fill_id(data["execID"].GetString());
    fill->set_fee(
        stod(data["execFeeEv"].GetString()) /
        ((symbol == "BTCUSD" || symbol == "cETHUSD") ? 1e8 : 1e4));
    if (exec_status == "MakerFill") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (exec_status == "TakerFill") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexFuturesParser::ParseHedgedWsFillInfoList(const JsonDocument& document) {
  /*
  "orders_p":[
      {
          "accountID":"47054200003",
          "action":"New",
          "actionBy":"ByUser",
          "actionTimeNs":"1681295883853734395",
          "addedSeq":"1981249828",
          "apRp":"0",
          "bonusChangedAmountRv":"0",
          "bpRp":"0",
          "clOrdID":"1681295872515201840",
          "cl_req_code":"0",
          "closedPnlRv":"0",
          "closedSize":"0",
          "code":"0",
          "cumFeeRv":"0",
          "cumQty":"0",
          "cumValueRv":"0",
          "curAccBalanceRv":"150.0288988225",
          "curAssignedPosBalanceRv":"6.045753367686",
          "curBonusBalanceRv":"0",
          "curLeverageRr":"-10",
          "curPosSide":"Buy",
          "curPosSize":"0.002",
          "curPosTerm":"1",
          "curPosValueRv":"59.9802",
          "curRiskLimitRv":"2000000",
          "currency":"USDT",
          "cxlRejReason":"0",
          "displayQty":"0.001",
          "execFeeRv":"0",
          "execID":"00000000-0000-0000-0000-000000000000",
          "execPriceRp":"0",
          "execQty":"0",
          "execSeq":"1981249828",
          "execStatus":"New",
          "execValueRv":"0",
          "feeRateRr":"0",
          "leavesQty":"0.001",
          "leavesValueRv":"29.9776",
          "message":"No error",
          "ordStatus":"New",
          "ordType":"Limit",
          "orderID":"0e7f6710-60fe-4d45-9ac2-8e533bd05dc5",
          "orderQty":"0.001",
          "pegOffsetProportionRr":"0",
          "pegOffsetValueRp":"0",
          "posSide":"Merged",
          "priceRp":"29977.6",
          "relatedPosTerm":"1",
          "relatedReqNum":"1470",
          "side":"Sell",
          "slTrigger":"ByMarkPrice",
          "stopLossRp":"0",
          "stopPxRp":"0",
          "symbol":"BTCUSDT",
          "takeProfitRp":"0",
          "timeInForce":"PostOnly",
          "tpTrigger":"ByLastPrice",
          "transactTimeNs":"1681295883856252940",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("orders_p") || !document["orders_p"].IsArray()) {
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["orders_p"].GetArray()) {
    if (!data.HasMember("execStatus")) {
      LOG(ERROR) << "[ParseHedgedWsFillInfoList] execStatus not found";
      continue;
    }
    std::string exec_status = data["execStatus"].GetString();
    if (exec_status != "MakerFill" && exec_status != "TakerFill") {
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    std::string symbol = data["symbol"].GetString();
    fill->set_symbol(symbol);
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["priceRp"].GetString()));
    fill->set_qty(stod(data["orderQty"].GetString()));
    fill->set_fill_id(data["execID"].GetString());
    fill->set_fee(stod(data["execFeeRv"].GetString()));
    if (exec_status == "MakerFill") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (exec_status == "TakerFill") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexFuturesParser::ParseWsAccountInfoList(const JsonDocument& document) {
  /*
  "accounts":[
      {
          "accountBalanceEv":"1000462",
          "accountID":"47054200002",
          "bonusBalanceEv":"0",
          "currency":"USD",
          "totalUsedBalanceEv":"70371",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("accounts") || !document["accounts"].IsArray()) {
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Phemex");
  acc_balance->set_market_type("Futures");
  bool acc_balance_found = false;
  for (const auto& data : document["accounts"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("accountBalanceEv") ||
        !data.HasMember("totalUsedBalanceEv")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* currency_balance = acc_balance->add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    // https://github.com/phemex/phemex-api-docs/blob/master/Public-Contract-API-en.md#priceratiovalue-scales
    // https://phemex-docs.github.io/#query-trading-account-and-positions
    auto total = stod(data["accountBalanceEv"].GetString()) /
                 ((currency->currency() == "BTC" || currency->currency() == "ETH") ? 1e8 : 1e4);
    auto hold = stod(data["totalUsedBalanceEv"].GetString()) /
                ((currency->currency() == "BTC" || currency->currency() == "ETH") ? 1e8 : 1e4);
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);
    acc_balance_found = true;
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void PhemexFuturesParser::ParseHedgedWsAccountInfoList(const JsonDocument& document) {
  /*
  "accounts_p":[
      {
          "accountBalanceRv":"150.0288988225",
          "accountID":"47054200003",
          "bonusBalanceRv":"0",
          "currency":"USDT",
          "totalUsedBalanceRv":"6.045753367686",
          "userID":"4705420"
      }
  ],
  */
  if (!document.HasMember("accounts_p") || !document["accounts_p"].IsArray()) {
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Phemex");
  acc_balance->set_market_type("Futures");
  bool acc_balance_found = false;
  for (const auto& data : document["accounts_p"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("accountBalanceRv") ||
        !data.HasMember("totalUsedBalanceRv")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* currency_balance = acc_balance->add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(data["accountBalanceRv"].GetString());
    auto hold = stod(data["totalUsedBalanceRv"].GetString());
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);
    acc_balance_found = true;
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void PhemexFuturesParser::ParseWsPositionInfoList(const JsonDocument& document) {
  /*
  "positions":[
      {
          "accountID":"47054200002",
          "assignedPosBalanceEv":"70371",
          "avgEntryPriceEp":"5093",
          "bankruptCommEv":"1013",
          "bankruptPriceEp":"12496",
          "buyLeavesQty":"0",
          "buyLeavesValueEv":"0",
          "buyValueToCostEr":"10114000",
          "createdAtNs":"0",
          "crossSharedBalanceEv":"930091",
          "cumClosedPnlEv":"0",
          "cumFundingFeeEv":"0",
          "cumTransactFeeEv":"-13",
          "curTermRealisedPnlEv":"9",
          "currency":"USD",
          "dataVer":"225",
          "deleveragePercentileEr":"0",
          "displayLeverageEr":"69000000",
          "estimatedOrdLossEv":"0",
          "execSeq":"14714504136",
          "freeCostEv":"0",
          "freeQty":"27",
          "initMarginReqEr":"10000000",
          "lastFundingTime":"1681380886563963696",
          "lastTermEndTime":"1681380917947159034",
          "leverageEr":"-1000000000",
          "liquidationPriceEp":"12445",
          "maintMarginReqEr":"1000000",
          "makerFeeRateEr":"-2500",
          "markPriceEp":"5102",
          "orderCostEv":"0",
          "posCostEv":"69216",
          "positionMarginEv":"999449",
          "positionStatus":"Normal",
          "riskLimitEv":"2000000000",
          "sellLeavesQty":"0",
          "sellLeavesValueEv":"0",
          "sellValueToCostEr":"10126000",
          "side":"Sell",
          "size":"27",
          "symbol":"XRPUSD",
          "takerFeeRateEr":"40000",
          "term":"3",
          "transactTimeNs":"1681381873622057379",
          "unrealisedPnlEv":"-1155",
          "updatedAtNs":"0",
          "usedBalanceEv":"70371",
          "userID":"4705420",
          "valueEv":"687615"
      }
  ]
  */
  if (!document.HasMember("positions") || !document["positions"].IsArray()) {
    return;
  }

  RawPositionMap raw_position_map;
  if (!ParseRawPosition(document["positions"], &raw_position_map)) {
    return;
  }

  auto acc_position = result_.mutable_account_position();
  acc_position->Clear();
  acc_position->set_exchange("Phemex");
  acc_position->set_market_type("Futures");
  bool acc_position_found = false;
  for (auto& pair : raw_position_map) {
    auto* position = acc_position->add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
    acc_position_found = true;
  }

  if (acc_position_found) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
  }
}

void PhemexFuturesParser::ParseHedgedWsPositionInfoList(const JsonDocument& document) {
  /*
  "positions_p":[
      {
          "accountID":"47054200003",
          "assignedPosBalanceRv":"6.045753367686",
          "avgEntryPriceRp":"29990.1",
          "bankruptCommRv":"0.00000012",
          "bankruptPriceRp":"0.1",
          "buyLeavesQty":"0",
          "buyLeavesValueRv":"0",
          "buyValueToCostRr":"0.10114",
          "createdAtNs":"0",
          "crossSharedBalanceRv":"143.983145454814",
          "cumClosedPnlRv":"0.0259",
          "cumFundingFeeRv":"0",
          "cumTransactFeeRv":"-0.0029988225",
          "curTermRealisedPnlRv":"0.0288988225",
          "currency":"USDT",
          "dataVer":"1470",
          "deleveragePercentileRr":"0",
          "displayLeverageRr":"0.39979098",
          "estimatedOrdLossRv":"0",
          "execSeq":"1981249828",
          "freeCostRv":"0",
          "freeQty":"-0.002",
          "initMarginReqRr":"0.1",
          "lastFundingTime":"1681286400000000000",
          "lastTermEndTime":"0",
          "leverageRr":"-10",
          "liquidationPriceRp":"0.1",
          "maintMarginReqRr":"0.005",
          "makerFeeRateRr":"-0.000025",
          "markPriceRp":"29982.427970157",
          "minPosCostRv":"0",
          "orderCostRv":"0",
          "posCostRv":"6.030409308",
          "posMode":"OneWay",
          "posSide":"Merged",
          "positionMarginRv":"150.0288987025",
          "positionStatus":"Normal",
          "riskLimitRv":"2000000",
          "sellLeavesQty":"0",
          "sellLeavesValueRv":"0",
          "sellValueToCostRr":"0.10126",
          "side":"Buy",
          "size":"0.002",
          "symbol":"BTCUSDT",
          "takerFeeRateRr":"0.0004",
          "term":"1",
          "transactTimeNs":"1681295883856252940",
          "unrealisedPnlRv":"-0.015344059686",
          "updatedAtNs":"0",
          "usedBalanceRv":"6.045753367686",
          "userID":"4705420",
          "valueRv":"59.9802"
      }
  ]
  */
  if (!document.HasMember("positions_p") || !document["positions_p"].IsArray()) {
    return;
  }

  RawPositionMap raw_position_map;
  if (!ParseRawPosition(document["positions_p"], &raw_position_map)) {
    return;
  }

  auto acc_position = result_.mutable_account_position();
  acc_position->Clear();
  acc_position->set_exchange("Phemex");
  acc_position->set_market_type("Futures");
  bool acc_position_found = false;
  for (auto& pair : raw_position_map) {
    auto* position = acc_position->add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
    acc_position_found = true;
  }

  if (acc_position_found) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
  }
}

void PhemexFuturesParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  ParseWsOrderInfoList(document);
  ParseHedgedWsOrderInfoList(document);
  ParseWsFillInfoList(document);
  ParseHedgedWsFillInfoList(document);
  ParseWsAccountInfoList(document);
  ParseHedgedWsAccountInfoList(document);
  ParseWsPositionInfoList(document);
  ParseHedgedWsPositionInfoList(document);
}

auto PhemexFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  if (msg.http_context->query_type != OrderSubTopicId::TRANSFER) {
    ParseHeader(msg.http_context);
    if (rate_limit_contract_.success) {
      mb_->PublishEvent(
          "phemex/rate_limit_contract",
          reinterpret_cast<void*>(rate_limit_contract_.rate_limit_remain));
    }
    if (enable_time_contract_.success) {
      mb_->PublishEvent(
          "phemex/enable_time_contract",
          reinterpret_cast<void*>(enable_time_contract_.enable_time));
    }
    if (rate_limit_others_.success) {
      mb_->PublishEvent(
          "phemex/rate_limit_others",
          reinterpret_cast<void*>(rate_limit_others_.rate_limit_remain));
    }
    if (enable_time_others_.success) {
      mb_->PublishEvent(
          "phemex/enable_time_others",
          reinterpret_cast<void*>(enable_time_others_.enable_time));
    }
  }

  if (msg.mea.api == "v1-linear") {
    switch (msg.http_context->query_type) {
      case OrderSubTopicId::SUBMIT_RESPONSE:
        ParseSubmitResponse();
        break;
      case OrderSubTopicId::CANCEL_RESPONSE:
        ParseCancelResponse();
        break;
      case OrderSubTopicId::REST_ACCOUNTS:
        ParseHedgedRestAccountInfo();
        break;
      case OrderSubTopicId::REST_OPEN_ORDERS:
        ParseHedgedRestOpenOrders();
        break;
      case OrderSubTopicId::REST_DONE_ORDERS:
        ParseHedgedRestHistoryOrders();
        break;
      case OrderSubTopicId::REST_FILL_ORDERS:
        ParseHedgedRestFillOrders();
        break;
      case OrderSubTopicId::TRANSFER:
        ParseRestTransfer();
        break;
      default: {
        LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
        result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
        break;
      }
    }

    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNTS:
      ParseRestAccountInfo();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrders();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseRestHistoryOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillOrders();
      break;
    case OrderSubTopicId::TRANSFER:
      ParseRestTransfer();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::phemex_futures::order_v1
